package ssllink

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"runtime"
	"slapp3/backend/config"
	"slapp3/backend/sslink/proto"
	"strings"

	"github.com/elastic/go-sysinfo"
	"go.uber.org/zap"
)

// Global variables
// Prof 是全局配置，包含了连接信息和认证信息，改变时需要调用SetProfile重新配置
var (
	Prof         = &GlobalProfile{Initialized: false}
	reqHeaders   = make(map[string]string)
	WebVpnCookie string
)

const (
	tplInit = iota
	tplAuthReply
)

func init() {
	host, _ := sysinfo.Host()
	info := host.Info()
	Prof.ComputerName = info.Hostname
	Prof.UniqueId = info.UniqueID

	os := info.OS
	Prof.DeviceType = os.Name
	if runtime.GOOS == "windows" {
		Prof.PlatformVersion = os.Build
	} else {
		Prof.PlatformVersion = strings.Split(os.Version, " ")[0]
	}
}

type stat struct {
	// be sure to use the double type when parsing
	BytesSent     uint64 `json:"bytesSent"`
	BytesReceived uint64 `json:"bytesReceived"`
}

type GlobalProfile struct {
	Host               string `json:"-"` // 只需要ip(域名)和端口
	Username           string `json:"-"`
	Password           string `json:"-"`
	Group              string `json:"-"`
	SecretKey          string `json:"-"`
	InsecureSkipVerify bool   `json:"-"`
	NoDTLS             bool   `json:"-"`
	Initialized        bool   `json:"-"`
	AppVersion         string `json:"-"`
	MacAddress         string `json:"-"`
	TunnelGroup        string `json:"-"`
	GroupAlias         string `json:"-"`
	ConfigHash         string `json:"-"`
	ComputerName       string `json:"-"` // 由init 函数设置
	DeviceType         string `json:"-"` // 由init 函数设置
	PlatformVersion    string `json:"-"` // 由init 函数设置
	UniqueId           string `json:"-"` // 由init 函数设置
	AuthPath           string `json:"-"`
	Version            string `json:"-"`
}

// SetCommonHeader 认证和建立隧道都需要的 HTTP Header
// ocserv worker-http.c case HEADER_USER_AGENT 通过 strncasecmp() 函数比较前 n 个字符
func SetCommonHeader(req *http.Request, config *config.Config) {
	// TODO: 这里版本号应该从配置中读取·
	req.Header.Set("User-Agent", fmt.Sprintf("AnyConnect %s %s", FirstUpper(runtime.GOOS+"_"+runtime.GOARCH), Prof.Version))
	req.Header.Set("Content-Type", "application/xml")
}

var templateInit = `<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="init" aggregate-auth-version="2">
    <version who="vpn">{{.AppVersion}}</version>
    <device-id computer-name="{{.ComputerName}}" device-type="{{.DeviceType}}" platform-version="{{.PlatformVersion}}" unique-id="{{.UniqueId}}"></device-id>
</config-auth>`

// https://datatracker.ietf.org/doc/html/draft-mavrogiannopoulos-openconnect-03#section-2.1.2.2
var templateAuthReply = `<?xml version="1.0" encoding="UTF-8"?>
<config-auth client="vpn" type="auth-reply" aggregate-auth-version="2">
    <version who="vpn">{{.AppVersion}}</version>
    <device-id computer-name="{{.ComputerName}}" device-type="{{.DeviceType}}" platform-version="{{.PlatformVersion}}" unique-id="{{.UniqueId}}"></device-id>
    <opaque is-for="sg">
        <tunnel-group>{{.TunnelGroup}}</tunnel-group>
        <group-alias>{{.GroupAlias}}</group-alias>
        <config-hash>{{.ConfigHash}}</config-hash>
    </opaque>
    <mac-address-list>
        <mac-address public-interface="true">{{.MacAddress}}</mac-address>
    </mac-address-list>
    <auth>
        <username>{{.Username}}</username>
        <password>{{.Password}}</password>
    </auth>
    <group-select>{{.Group}}</group-select>
</config-auth>`

// 渲染模板并发送请求
func (s *Sslink) tplPost(typ int, path string, dtd *proto.DTD) error {
	tplBuffer := new(bytes.Buffer)

	if typ == tplInit {
		t, err := template.New("init").Parse(templateInit)
		if err != nil {
			s.log.Error("Failed to parse init template", zap.Error(err))
			return fmt.Errorf("failed to parse init template: %w", err)
		}
		err = t.Execute(tplBuffer, Prof)
		if err != nil {
			s.log.Error("Failed to execute init template", zap.Error(err))
			return fmt.Errorf("failed to execute init template: %w", err)
		}
	} else {
		t, err := template.New("auth_reply").Parse(templateAuthReply)
		if err != nil {
			s.log.Error("Failed to parse auth reply template", zap.Error(err))
			return fmt.Errorf("failed to parse auth reply template: %w", err)
		}
		if err := t.Execute(tplBuffer, Prof); err != nil {
			s.log.Error("Failed to execute auth reply template", zap.Error(err))
			return fmt.Errorf("failed to execute auth reply template: %w", err)
		}
	}

	url := fmt.Sprintf("%s%s", Prof.Host, path)
	if Prof.SecretKey != "" {
		url += "?" + Prof.SecretKey
	}
	url = fmt.Sprintf("https://%s", url)
	req, err := http.NewRequest("POST", url, tplBuffer)
	if err != nil {
		s.Conn.Close()
		s.log.Error("Failed to create request", zap.Error(err))
		return err
	}

	SetCommonHeader(req, s.config)
	for k, v := range reqHeaders {
		req.Header[k] = []string{v}
	}
	err = req.Write(s.Conn)
	if err != nil {
		s.Conn.Close()
		return err
	}

	var resp *http.Response
	resp, err = http.ReadResponse(s.BufR, req)
	if err != nil {
		s.Conn.Close()
		return err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		s.Conn.Close()
		return err
	}

	// s.log.Debug("Response body", zap.String("body", string(body)))
	if resp.StatusCode == http.StatusOK {
		err = xml.Unmarshal(body, dtd)
		if err != nil {
			s.log.Error("Failed to unmarshal response body", zap.Error(err))
			return fmt.Errorf("failed to unmarshal response body: %w", err)
		}
		if dtd.Type == "complete" && dtd.SessionToken == "" {
			// 兼容 ocserv
			cookies := resp.Cookies()
			if len(cookies) != 0 {
				for _, c := range cookies {
					if c.Name == "webvpn" {
						WebVpnCookie = c.Value
						break
					}
				}
			}
		}
		return nil
	}
	s.Conn.Close()
	return fmt.Errorf("auth error %s", resp.Status)
}

// =================路由record==================
type Record struct {
	Filename string
	Contents []string
}

func NewRecord(filename string) *Record {
	return &Record{
		Filename: filename,
		Contents: make([]string, 0),
	}
}

func (r *Record) readLines() error {
	if _, err := os.Stat(r.Filename); err != nil {
		return nil
	}

	f, err := os.OpenFile(r.Filename, os.O_RDONLY, 0600)
	if err != nil {
		return err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		if tmp := scanner.Text(); len(tmp) != 0 {
			r.Contents = append(r.Contents, tmp)
		}
	}

	return nil
}

func (r *Record) Write(content string, prepend bool) error {
	if prepend {
		err := r.readLines()
		if err != nil {
			return err
		}
	}

	f, err := os.OpenFile(r.Filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
	if err != nil {
		return err
	}
	defer f.Close()

	writer := bufio.NewWriter(f)
	writer.WriteString(fmt.Sprintf("%s\n", content))

	if prepend {
		for _, line := range r.Contents {
			_, err = writer.WriteString(fmt.Sprintf("%s\n", line))
			if err != nil {
				return err
			}
		}
	}

	if err = writer.Flush(); err != nil {
		return err
	}

	return nil
}

// =================路由record end==================
